﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using Citrix.Cmp;
using Citrix.XaAdminMobile.Model;
using System.Diagnostics;
using System.Windows.Input;
using Citrix.XaAdminMobile.Util;

namespace Citrix.XaAdminMobile.ViewModel
{
    /// <summary>
    /// This is the root object in the v-m layer.
    /// </summary>
    internal class ViewModelManager : INotifyPropertyChanged
    {
        /// <summary>
        /// Fired whenever the page(s) to be displayed to the user have changed.
        /// </summary>
        public event EventHandler PageChanged;

        /// <summary>
        /// A ref to the App instance.
        /// </summary>
        private readonly App theApp;

        /// <summary>
        /// The root object in the model layer.
        /// </summary>
        private readonly IModelBase modelBase;

        /// <summary>
        /// The v-m for the servers view.
        /// </summary>
        private readonly ServersViewModel serversVm;

        /// <summary>
        /// The v-m for the sessions view.
        /// </summary>
        private readonly SessionsViewModel sessionsVm;

        /// <summary>
        /// The v-m for configuring app settings.
        /// </summary>
        private readonly SettingsViewModel settingsVm;

        /// <summary>
        /// The v-m for the add XenApp server view.
        /// </summary>
        private readonly AddServerViewModel addServerVm;

        /// <summary>
        /// The v-m for the master navigation toolbar.
        /// </summary>
        private readonly NavToolbarViewModel masterNavigationVm;

        /// <summary>
        /// The currently active navigation implementation. We flip between different navigation
        /// implementations based on the layout we are currently using.
        /// </summary>
        private INavigation activeNavigation;

        /// <summary>
        /// Handler for the current navigation implementation changing its page.
        /// </summary>
        private EventHandler pageChangedHandler;

        /// <summary>
        /// Initialisation ctor.
        /// </summary>
        /// <param name="app">The application object.</param>
        /// <param name="modelSource">The root model object.</param>
        /// <exception cref="ArgumentNullException">Thrown if any arg is null.</exception>
        public ViewModelManager(App app, IModelBase modelSource)
        {
            if (null == app)
            {
                throw new ArgumentNullException("app");
            }
            if (null == modelSource)
            {
                throw new ArgumentNullException("modelSource");
            }

            this.theApp = app;
            this.modelBase = modelSource;
            this.pageChangedHandler = new EventHandler(ActiveNavigation_PageChanged);
            this.masterNavigationVm = new NavToolbarViewModel(this);
            this.serversVm = new ServersViewModel(this, this.modelBase.Servers);
            this.sessionsVm = new SessionsViewModel(this, this.modelBase.Sessions);
            this.settingsVm = new SettingsViewModel(this, this.modelBase);
            this.addServerVm = new AddServerViewModel(this, this.modelBase.ServerConfig);
            
            this.activeNavigation = new PhoneNavigation(this, this.sessionsVm);
            this.activeNavigation.PageChanged += this.pageChangedHandler;

            this.DefaultBackCommand = new DelegateCommand(() =>
                {
                    this.activeNavigation.NavigateBack();
                    RefreshButtonRedirection();
                });
            this.theApp.CmpWpfApi.Input.ButtonPressed += new EventHandler<ButtonPressedArgs>(CmpInput_ButtonPressed);
            this.theApp.CmpWpfApi.LayoutTracker.PropertyChanged += new PropertyChangedEventHandler(LayoutTracker_PropertyChanged);

            UpdateLayout();
        }

        /// <summary>
        /// Returns a refernce to the global app object.
        /// </summary>
        public App App
        {
            get { return this.theApp; }
        }

        /// <summary>
        /// The default navigate back command. This will trigger navigation back to the previous
        /// page in the navigation history. Only use this default back command if your page can
        /// navigate back while in any state.
        /// </summary>
        public DelegateCommand DefaultBackCommand
        {
            get;
            private set;
        }

        /// <summary>
        /// The active navigation implementation. We swap the navigation implementation based
        /// on the active layout.
        /// </summary>
        public INavigation ActiveNavigation
        {
            get { return this.activeNavigation; }
        }

        /// <summary>
        /// The master navigation toolbar v-m.
        /// </summary>
        public NavToolbarViewModel NavToolbarViewModel
        {
            get { return this.masterNavigationVm; }
        }

        /// <summary>
        /// Navigates the app to the specified page.
        /// </summary>
        /// <param name="target">The page to navigate to.</param>
        public void NavigateToPage(IPage target)
        {
            AssertUiThread();
            this.activeNavigation.NavigateForward(target);
            RefreshButtonRedirection();
        }

        /// <summary>
        /// The add XenApp server page v-m object.
        /// </summary>
        public AddServerViewModel AddServerViewModel
        {
            get { return this.addServerVm; }
        }

        /// <summary>
        /// The servers page v-m object.
        /// </summary>
        public ServersViewModel ServersViewModel
        {
            get { return this.serversVm; }
        }

        /// <summary>
        /// The sessions page v-m object.
        /// </summary>
        public SessionsViewModel SessionsViewModel
        {
            get { return this.sessionsVm; }
        }

        /// <summary>
        /// The settings page v-m object.
        /// </summary>
        public SettingsViewModel SettingsViewModel
        {
            get { return this.settingsVm; }
        }

        /// <summary>
        /// Checks whether the current thread is the UI thread and asserts if it isn't. This is
        /// used both as a debug check as well as a declarative statement to other devs that the
        /// current code block has to run in the UI thread. There's no graceful way to recover
        /// when this condition is violated and it indicates a dev bug rather than an expected
        /// operating conditions (hence the assert semantics). In a release build WPF will throw
        /// at some point if this condition ever did get violated.
        /// </summary>
        [Conditional("DEBUG")]
        public void AssertUiThread()
        {
            if (System.Threading.Thread.CurrentThread != this.theApp.Dispatcher.Thread)
            {
                Debug.Assert(System.Threading.Thread.CurrentThread == this.theApp.Dispatcher.Thread,
                    "Operation not running in the UI thread.");
            }
        }

        /// <summary>
        /// Dispatches the supplied action onto the main UI thread.
        /// </summary>
        /// <param name="action">The action to dispatch. Null actions are ignored.</param>
        public void UiDispatch(Action action)
        {
            if (null != action)
            {
                if (System.Threading.Thread.CurrentThread == this.theApp.Dispatcher.Thread)
                {
                    action();
                }
                else
                {
                    this.theApp.Dispatcher.BeginInvoke(action);
                }
            }
        }

        /// <summary>
        /// Event handler for the user pressing a physical button on the mobile device that we
        /// have redirected up to the server.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Event args.</param>
        private void CmpInput_ButtonPressed(object sender, ButtonPressedArgs e)
        {
            if (e.Button == ButtonId.Back)
            {
                this.activeNavigation.NotifyButtonPressed(e.Button);
            }
        }

        /// <summary>
        /// Handles notifications from the CMP SDK that the layout of the app has changed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void LayoutTracker_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "FormFactor")
            {
                UpdateLayout();
            }
        }

        /// <summary>
        /// Sets up the layout of the app to either the phone or tablet layout.
        /// </summary>
        private void UpdateLayout()
        {
            DeviceType formFactor = this.theApp.CmpWpfApi.LayoutTracker.FormFactor;

            NavigationLayout layout = NavigationLayout.Phone;
            if (DeviceType.Tablet == formFactor)
            {
                layout = NavigationLayout.Tablet;
            }

            INavigation newNavigation = this.activeNavigation.Convert(layout);
            if (!object.ReferenceEquals(newNavigation, this.activeNavigation))
            {
                this.activeNavigation.PageChanged -= this.pageChangedHandler;
                this.activeNavigation = newNavigation;
                this.activeNavigation.PageChanged += this.pageChangedHandler;
                OnPropertyChanged("ActiveNavigation");
                RefreshButtonRedirection();
            }
        }

        /// <summary>
        /// Route the event to our own public page changed event.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ActiveNavigation_PageChanged(object sender, EventArgs e)
        {
            this.PageChanged.Raise(this, e);
        }

        /// <summary>
        /// Refreshes the redirection of buttons on the client device. This should be called
        /// when ever there is a change in navigation state.
        /// </summary>
        private void RefreshButtonRedirection()
        {
            this.theApp.CmpSynchroniser.BackButtonTarget = this.activeNavigation.BackButtonTarget;
        }

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Fired whenever a public property changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Fires the property change event.
        /// </summary>
        /// <param name="propertyName">The name of the property that changed.</param>
        private void OnPropertyChanged(string propertyName)
        {
            NotifyHelper.VerifyProperty(propertyName, this);
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged.Raise(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion // INotifyPropertyChanged Members
    }
}
